package source.javadog.mysource;

import org.redkale.util.Attribute;
import source.javadog.util.IAttribute;
import source.javadog.util.MyCreator;

import javax.persistence.*;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Blob;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;

/**
 * Author: yuyong
 * Date: 2018-09-12 下午4:51
 * Description:
 */
public class MyEntityInfo<T> {

    //全局静态资源
    private static final ConcurrentHashMap<Class, MyEntityInfo> entityInfos = new ConcurrentHashMap<>();

    //日志
    private static final Logger logger = Logger.getLogger(MyEntityInfo.class.getSimpleName());

    //Entity类名
    private final Class<T> type;

    //所属的DataSource
    final MyDataSource source;

    //类对应的数据表名, 如果是VirtualEntity 类， 则该字段为null
    final String table;

    //Entity构建器
    private final MyCreator<T> creator;

    //所有可更新字段，即排除了主键字段和标记为@Column(updatable=false)的字段
    private final Map<String, IAttribute<T, Serializable>> updateAttributeMap = new HashMap<>();

    //key是field的name， 不是sql字段。
    //存放所有与数据库对应的字段， 包括主键
    private final HashMap<String, IAttribute<T, Serializable>> attributeMap = new HashMap<>();

    //主键
    final IAttribute<T, Serializable> primary;

    //key是field的name， value是Column的别名，即数据库表的字段名
    //只有field.name 与 Column.name不同才存放在aliasmap里.
    private final Map<String, String> aliasmap;

    //存放所有与数据库对应的字段， 包括主键
    final IAttribute<T, Serializable>[] attributes;

    //数据库中所有字段
    private final IAttribute<T, Serializable>[] queryAttributes;

    //数据库中所有可新增字段
    final IAttribute<T, Serializable>[] insertAttributes;

    //数据库中所有可更新字段
    final IAttribute<T, Serializable>[] updateAttributes;

    //新增SQL， 含 ？，即排除了自增长主键和标记为&#064;Column(insertable=false)的字段
    private final String insertPrepareSQL;

    //新增SQL， 含 $，即排除了自增长主键和标记为&#064;Column(insertable=false)的字段
    private final String insertDollarPrepareSQL;

    //新增SQL， 含 :name，即排除了自增长主键和标记为&#064;Column(insertable=false)的字段
    private final String insertNamesPrepareSQL;

    //根据主键更新所有可更新字段的SQL，含 ？
    private final String updatePrepareSQL;

    //根据主键更新所有可更新字段的SQL，含 $
    private final String updateDollarPrepareSQL;

    //根据主键更新所有可更新字段的SQL，含 :name
    private final String updateNamesPrepareSQL;

    //根据主键删除记录的SQL，含 ？
    private final String deletePrepareSQL;

    //根据主键删除记录的SQL，含 $
    private final String deleteDollarPrepareSQL;

    //根据主键删除记录的SQL，含 :name
    private final String deleteNamesPrepareSQL;

    //根据主键查找单个对象的SQL， 含 ？
    private final String queryPrepareSQL;

    //根据主键查找单个对象的SQL， 含 $
    private final String queryDollarPrepareSQL;

    //根据主键查找单个对象的SQL， 含 :name
    private final String queryNamesPrepareSQL;

    //是否由数据库生成主键值
    final boolean autoGenerated;

    //是否UUID主键
    final boolean autouuid;

    //用于反向LIKE使用
    final String containSQL;

    //用于反向LIKE使用
    final String notcontainSQL;

    //用于判断表不存在的使用, 多个SQLState用;隔开
    final String tablenotexistSqlstates;

    //用于复制表结构使用
    final String tablecopySQL;

    /**
     * 获取Entity类名
     *
     * @return Class
     */
    public Class<T> getType() {
        return type;
    }

    /**
     * 加载EntityInfo
     *
     * @param clazz          Entity类
     * @param conf           配置信息, persistence.xml中的property节点值
     * @param source         DataSource,可为null
     */
    static <T> MyEntityInfo<T> load(Class<T> clazz, final Map<String,String> conf, MyDataSource source) {
        //todo 不同步会怎么样
        MyEntityInfo rs = entityInfos.get(clazz);
        if (rs != null) return rs;
        synchronized (entityInfos) {
            rs = entityInfos.get(clazz);
            if (rs == null) {
                rs = new MyEntityInfo(clazz, conf, source);
                entityInfos.put(clazz, rs);
            }
            return rs;
        }
    }

    /**
     * 构造函数
     *
     * @param type           Entity类
     * @param source         DataSource,可为null
     */
    private MyEntityInfo(Class<T> type, Map<String,String> conf, MyDataSource source) {
        this.type = type;
        this.source = source;
        //---------------------------------------------
        //todo 日志打印
        //---------------------------------------------
        Table t = type.getAnnotation(Table.class);
        //---------------------------------------------
        //todo 内存与虚拟类
        //---------------------------------------------
        //todo 类加载器
        //---------------------------------------------
        //todo table类没太看明白
        if (t != null && !t.name().isEmpty() && t.name().indexOf('.') >= 0) throw new RuntimeException(type + " have illegal table.name on @Table");
        this.table = (t == null) ? type.getSimpleName().toLowerCase() : (t.catalog().isEmpty()) ? (t.name().isEmpty() ? type.getSimpleName().toLowerCase() : t.name()) : (t.catalog() + '.' + (t.name().isEmpty() ? type.getSimpleName().toLowerCase() : t.name()));
        //---------------------------------------------
        //todo 分库分表
        //---------------------------------------------
        //todo creator 不知道是啥
        this.creator = MyCreator.create(type);
        //---------------------------------------------
        //todo Attribute
        IAttribute idAttr0 = null;
        Map<String, String> aliasmap0 = null;
        //实体类
        Class cltmp = type;
        Set<String> fields = new HashSet<>();
        //查询的属性
        List<IAttribute<T, Serializable>> queryattrs = new ArrayList<>();
        //插入的字段
        List<String> insertcols = new ArrayList<>();
        //插入的属性
        List<IAttribute<T, Serializable>> insertattrs = new ArrayList<>();
        //更新的字段
        List<String> updatecols = new ArrayList<>();
        //更新的属性
        List<IAttribute<T, Serializable>> updateattrs = new ArrayList<>();
        boolean auto = false;
        boolean uuid = false;

        do {
            for (Field field : cltmp.getDeclaredFields()) {
                //不操作static，final，
                if (Modifier.isStatic(field.getModifiers())) continue;
                if (Modifier.isFinal(field.getModifiers())) continue;
                //@Transient代表与数据库无关
                if (field.getAnnotation(Transient.class) != null) continue;
                //可能有父类字段重复，以子类为准
                if (fields.contains(field.getName())) continue;
                //获取字段的名字和注解中的名字，以注解为准，没有往下取
                final String fieldname = field.getName();
                final Column col = field.getAnnotation(Column.class);
                final String sqlfield = col == null || col.name().isEmpty() ? fieldname : col.name();
                //fieldname与col.name()不一致时，即有别名，存储起来
                if (!fieldname.equals(sqlfield)) {
                    if (aliasmap0 == null) aliasmap0 = new HashMap<>();
                    aliasmap0.put(fieldname, sqlfield);
                }
                //creator相关有点深，暂时不看
                IAttribute attr;
                try {
                    attr = IAttribute.create(cltmp, field);
                } catch (RuntimeException e) {
                    continue;
                }
                if (field.getAnnotation(Id.class) != null && idAttr0 == null) {
                    idAttr0 = attr;
                    //@GeneratedValue用在主键上，用于指定生成策略，如自增长
                    //不能用于派生主键，如父类或其他字段也有这个注解，只认第一个，因此if需要判断 && idAttr0 == null
                    GeneratedValue gv = field.getAnnotation(GeneratedValue.class);
                    auto = gv != null;
                    //string类型主键用uuid，否则用自增长   没有加@GeneratedValue时即不自增长也不uuid
                    if (gv != null && field.getType() == String.class) { //UUID
                        uuid = true;
                        auto = false;
                    }
                    //没有加@GeneratedValue || 主键String时，有点懵逼？
                    if (!auto) {
                        insertcols.add(sqlfield);
                        insertattrs.add(attr);
                    }
                } else {
                    //非主键字段，该插插
                    if (col == null || col.insertable()) {
                        insertcols.add(sqlfield);
                        insertattrs.add(attr);
                    }
                    if (col == null || col.updatable()) {
                        updatecols.add(sqlfield);
                        updateattrs.add(attr);
                        //用于？为毛没有insertAttributeMap
                        updateAttributeMap.put(fieldname, attr);
                    }
                }
                //只有insertable和updatable，这段循环代码用于读所有字段，根据相关注解属性分门别类的放到map中
                //attribute与creator一套的，暂时不管
                queryattrs.add(attr);
                fields.add(fieldname);
                attributeMap.put(fieldname, attr);
            }
        } while ((cltmp = cltmp.getSuperclass()) != Object.class);
        if (idAttr0 == null) throw new RuntimeException(type.getName() + " have no primary column by @javax.persistence.Id");
        //蛮有意思，类里面有一个map集合，存所有本类的对象，每次load的时候去查，没有再加载放在map集合里面
        this.primary = idAttr0;
        this.aliasmap = aliasmap0;
        //todo 数组与集合的选择
        this.attributes = attributeMap.values().toArray(new IAttribute[attributeMap.size()]);
        this.queryAttributes = queryattrs.toArray(new IAttribute[queryattrs.size()]);
        this.insertAttributes = insertattrs.toArray(new IAttribute[insertattrs.size()]);
        this.updateAttributes = updateattrs.toArray(new IAttribute[updateattrs.size()]);


        //todo 根据需要删除creator是否需要
        StringBuilder insertsb = new StringBuilder();
        StringBuilder insertsbjdbc = new StringBuilder();
        StringBuilder insertsbdollar = new StringBuilder();
        StringBuilder insertsbnames = new StringBuilder();
        int index = 0;
        for (String col : insertcols) {
            if (index > 0) {
                insertsb.append(',');
                insertsbjdbc.append(',');
                insertsbdollar.append(',');
                insertsbnames.append(',');
            }
            insertsb.append(col);
            insertsbjdbc.append('?');
            insertsbdollar.append("$").append(++index);
            insertsbnames.append(":").append(col);
        }
        this.insertPrepareSQL = "INSERT INTO " + table + "(" + insertsb + ") VALUES(" + insertsbjdbc + ")";
        this.insertDollarPrepareSQL = "INSERT INTO " + table + "(" + insertsb + ") VALUES(" + insertsbdollar + ")";
        this.insertNamesPrepareSQL = "INSERT INTO " + table + "(" + insertsb + ") VALUES(" + insertsbnames + ")";
        StringBuilder updatesb = new StringBuilder();
        StringBuilder updatesbdollar = new StringBuilder();
        StringBuilder updatesbnames = new StringBuilder();
        index = 0;
        for (String col : updatecols) {
            if (index > 0) {
                updatesb.append(", ");
                updatesbdollar.append(", ");
                updatesbnames.append(", ");
            }
            updatesb.append(col).append(" = ?");
            updatesbdollar.append(col).append(" = ").append("$").append(++index);
            updatesbnames.append(col).append(" = :").append(col);
        }
        //拼接sql
        this.updatePrepareSQL = "UPDATE " + table + " SET " + updatesb + " WHERE " + getPrimarySQLColumn(null) + " = ?";
        this.updateDollarPrepareSQL = "UPDATE " + table + " SET " + updatesbdollar + " WHERE " + getPrimarySQLColumn(null) + " = $" + (++index);
        this.updateNamesPrepareSQL = "UPDATE " + table + " SET " + updatesbnames + " WHERE " + getPrimarySQLColumn(null) + " = :" + getPrimarySQLColumn(null);
        this.deletePrepareSQL = "DELETE FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = ?";
        this.deleteDollarPrepareSQL = "DELETE FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = $1";
        this.deleteNamesPrepareSQL = "DELETE FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = :" + getPrimarySQLColumn(null);
        this.queryPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = ?";
        this.queryDollarPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = $1";
        this.queryNamesPrepareSQL = "SELECT * FROM " + table + " WHERE " + getPrimarySQLColumn(null) + " = :" + getPrimarySQLColumn(null);

        this.autoGenerated = auto;
        this.autouuid = uuid;
        //----------------cache--------------
        //todo cache

        //todo 干嘛的
        if (conf == null) conf = new HashMap<>();
        this.containSQL = conf.getOrDefault(MyDataSources.JDBC_CONTAIN_SQLTEMPLATE, "LOCATE(${keystr}, ${column}) > 0");
        this.notcontainSQL = conf.getOrDefault(MyDataSources.JDBC_NOTCONTAIN_SQLTEMPLATE, "LOCATE(${keystr}, ${column}) = 0");

        this.tablenotexistSqlstates = ";" + conf.getOrDefault(MyDataSources.JDBC_TABLENOTEXIST_SQLSTATES, "42000;42S02") + ";";
        this.tablecopySQL = conf.getOrDefault(MyDataSources.JDBC_TABLECOPY_SQLTEMPLATE, "CREATE TABLE ${newtable} LIKE ${oldtable}");
    }

    /**
     * 获取主键字段的带有表别名的表字段名
     *
     * @param tabalis 表别名
     *
     * @return String
     */
    public String getPrimarySQLColumn(String tabalis) {
        return getSQLColumn(tabalis, this.primary.field());
    }

    /**
     * 根据field字段名获取数据库对应的字段名
     *
     * @param tabalis   表别名
     * @param fieldname 字段名
     *
     * @return String
     */
    public String getSQLColumn(String tabalis, String fieldname) {
        return this.aliasmap == null ? (tabalis == null ? fieldname : (tabalis + '.' + fieldname))
                : (tabalis == null ? aliasmap.getOrDefault(fieldname, fieldname) : (tabalis + '.' + aliasmap.getOrDefault(fieldname, fieldname)));
    }

    /**
     * 获取Entity的INSERT SQL
     *
     * @param bean Entity对象
     *
     * @return String
     */
    public String getInsertPrepareSQL(T bean) {
        return insertPrepareSQL;
    }

    /**
     * 获取主键字段的Attribute
     *
     * @return Attribute
     */
    public IAttribute<T, Serializable> getPrimary() {
        return this.primary;
    }

    public String getTable() {
        return table;
    }

    /**
     * 将一行的ResultSet组装成一个Entity对象
     *
     * @param set  ResultSet
     *
     * @return Entity对象
     * @throws SQLException SQLException
     */
    protected T getValue(final ResultSet set) throws SQLException {
        T obj;
        IAttribute<T, Serializable>[] attrs = this.queryAttributes;
        obj = creator.create();
        for (IAttribute<T, Serializable> attr : attrs) {
            attr.set(obj, getFieldValue(attr, set));
        }
        return obj;
    }

    protected Serializable getFieldValue(IAttribute<T, Serializable> attr, final ResultSet set) throws SQLException {
        final Class t = attr.type();
        Serializable o;
        if (t == byte[].class) {
            Blob blob = set.getBlob(this.getSQLColumn(null, attr.field()));
            if (blob == null) {
                o = null;
            } else { //不支持超过2G的数据
                o = blob.getBytes(1, (int) blob.length());
            }
        } else {
            o = (Serializable) set.getObject(this.getSQLColumn(null, attr.field()));
            if (t.isPrimitive()) {
                if (o != null) {
                    if (t == int.class) {
                        o = ((Number) o).intValue();
                    } else if (t == long.class) {
                        o = ((Number) o).longValue();
                    } else if (t == short.class) {
                        o = ((Number) o).shortValue();
                    } else if (t == float.class) {
                        o = ((Number) o).floatValue();
                    } else if (t == double.class) {
                        o = ((Number) o).doubleValue();
                    } else if (t == byte.class) {
                        o = ((Number) o).byteValue();
                    } else if (t == char.class) {
                        o = (char) ((Number) o).intValue();
                    } else if (t == boolean.class) {
                        o = (Boolean) o;
                    }
                } else if (t == int.class) {
                    o = 0;
                } else if (t == long.class) {
                    o = 0L;
                } else if (t == short.class) {
                    o = (short) 0;
                } else if (t == float.class) {
                    o = 0.0f;
                } else if (t == double.class) {
                    o = 0.0d;
                } else if (t == byte.class) {
                    o = (byte) 0;
                } else if (t == boolean.class) {
                    o = false;
                } else if (t == char.class) {
                    o = (char) 0;
                }
            } else if (t == AtomicInteger.class) {
                if (o != null) {
                    o = new AtomicInteger(((Number) o).intValue());
                } else {
                    o = new AtomicInteger();
                }
            } else if (t == AtomicLong.class) {
                if (o != null) {
                    o = new AtomicLong(((Number) o).longValue());
                } else {
                    o = new AtomicLong();
                }
            }
        }
        return o;
    }

    public String getUpdatePrepareSQL() {
        return updatePrepareSQL;
    }
}
